<?php

namespace App\Http\Controllers;

use App\Http\Utils\Response;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class CouponsController extends Controller
{

    /**
     * @var int
     */
    protected $page_size = 3;

    const COUPONS_NOT_FOUND = '该优惠券、折扣券不存在';
    const COUPONS_SHOP_NOT_FOUND = '该商户(用户)没有优惠券、折扣券';
    const COUPONS_NOT_ENOUGH = '该优惠券、折扣券库存数量不足';
    const COUPONS_TOO_MANY = '拥有太多该优惠券、折扣券';
    const COUPONS_LEVEL_TOO_LOW = '等级太低';
    const COUPONS_SATISFY_AMOUNT = '该消费未满足该优惠券、折扣券指定金额';
    const COUPONS_DISTRIBUTION = '仅加入该推广的用户可以领取';
    const COUPONS_COMMODITY = '指定商品才可以使用该优惠券、折扣券';
    const COUPONS_NOT_EFFECT = '优惠券、折扣券未生效';
    const COUPONS_EXPIRY = '优惠券、折扣券已过期';
    const COUPONS_HAS_USED = '优惠券、折扣券已使用';
    const COUPONS_JUST_USE_BY_ONE = '同一商品只能使用一张优惠券、折扣券';

    /**
     *
     * @title 查询优惠券、折扣券
     * @curl get /coupons/index/{id}
     * @vars int id true 优惠券、折扣券ID
     * @back Model
     *
     * @param int $id
     * @return Response
     * @throws \Exception
     */
    public function index($id)
    {
        $coupons = DB::table('coupons')->where(['id' => $id])->first();
        if (is_null($coupons)) {
            throw new \Exception(self::COUPONS_NOT_FOUND, 404);
        }
        return $this->json($coupons);
    }

    /**
     * @title 查询优惠券、折扣券
     * @curl get /lists/{shop_id}/{page}
     * @vars int shop_id true 商户ID
     * @vars int page true 页数
     * @vars not_effective|effective|expiry type false 未生效|已生效|已过期
     * @back {total_page:int,current_page:int,coupons:Model}
     *
     * @param Request $request
     * @param int $shop_id
     * @param int $page
     * @return Response
     * @throws \Exception
     */
    public function lists(Request $request, $shop_id, $page)
    {
        $this->validate($request, [
            'type'      => 'in:not_effective,effective,expiry',
            'page_size' => 'integer|min:1',
        ]);
        $where[] = ['shop_id', '=', $shop_id,];
        $currentTime = time();
        switch ($type = $request->get('type', null)) {
            case 'not_effective':
                $where[] = ['effect_time', '>', $currentTime];
                break;
            case 'effective':
                $where[] = ['effect_time', '<', $currentTime];
                $where[] = ['expiry_time', '>', $currentTime];
                break;
            case 'expiry':
                $where[] = ['expiry_time', '<', $currentTime];
                break;
        }
        $base = DB::table('coupons')->where($where);
        $page_size = $request->input('page_size', $this->page_size);
        $count = $base->count('id');
        $totalPage = ceil($count / $page_size);
        if ($totalPage == 0) {
            throw new \Exception(self::COUPONS_SHOP_NOT_FOUND, 404);
        }
        $page = ($page <= 1) ? 1 : $page;
        $coupons = $base->orderBy('id', 'desc')->offset(($page - 1) * $page_size)->limit($page_size)->get();
        return $this->json([
            'page_size'    => (int)$page_size,
            'count'        => (int)$count,
            'total_page'   => $totalPage,
            'current_page' => (int)$page,
            'coupons'      => $coupons,
        ]);
    }

    /**
     * @param Request $request
     * @return Response
     */
    public function create(Request $request)
    {
        $this->validate($request, [
            'shop_id'            => 'required|alpha_num',
            'name'               => 'required|string|min:1|max:255',
            'total_count'        => 'required|integer|min:1',
            'type'               => 'required|in:amount,percent',
            'type_info'          => 'required|integer|min:1',
            'describe'           => 'required|string|max:255',
            'limit_amount'       => 'required|integer|min:0',
            'limit_commodity'    => 'required|integer|min:0',
            'limit_level'        => 'required|integer|min:0',
            'limit_count'        => 'required|integer|min:0',
            'limit_distribution' => 'required|integer|min:0',
            'effect_time'        => 'required|integer|min:0',
            'expiry_time'        => 'required|integer|min:0',
        ]);
        $data = $request->only([
            'shop_id',
            'name',
            'total_count',
            'type',
            'type_info',
            'describe',
            'limit_amount',
            'limit_commodity',
            'limit_level',
            'limit_count',
            'limit_distribution',
            'effect_time',
            'expiry_time',
        ]);
        $data['create_time'] = time();
        $data['update_time'] = time();
        $id = DB::table('coupons')->insertGetId($data);
        return $this->json($id);
    }

    /**
     * @param Request $request
     * @param int $id
     * @return Response
     * @throws \Exception
     */
    public function edit(Request $request, $id)
    {
        $coupons = DB::table('coupons')->where(['id' => $id])->first(['id']);
        if (is_null($coupons)) {
            throw new \Exception(self::COUPONS_NOT_FOUND, 404);
        }
        $this->validate($request, [
            'name'               => 'string|min:1|max:255',
            'total_count'        => 'integer|min:1',
            'type'               => 'in:amount,percent',
            'type_info'          => 'integer|min:1',
            'describe'           => 'string|max:255',
            'limit_amount'       => 'integer|min:0',
            'limit_commodity'    => 'integer|min:0',
            'limit_level'        => 'integer|min:0',
            'limit_count'        => 'integer|min:0',
            'limit_distribution' => 'integer|min:0',
            'effect_time'        => 'integer|min:0',
            'expiry_time'        => 'integer|min:0',
        ]);
        $data = $request->only([
            'name',
            'total_count',
            'type',
            'type_info',
            'describe',
            'limit_amount',
            'limit_commodity',
            'limit_level',
            'limit_count',
            'limit_distribution',
            'effect_time',
            'expiry_time',
        ]);
        $data['update_time'] = time();
        DB::table('coupons')->where(['id' => $id,])->update($data);
        return $this->json($data);
    }

    /**
     * @param int $id
     * @return Response
     * @throws \Exception
     */
    public function delete($id)
    {
        $coupons = DB::table('coupons')->where(['id' => $id])->first(['id']);
        if (is_null($coupons)) {
            throw new \Exception(self::COUPONS_NOT_FOUND, 404);
        }
        DB::table('coupons')->where(['id' => $id])->delete();
        return $this->json(null);
    }

    /*----------------------------------------------------------------------*/

    /**
     * @param int $user_id
     * @param int $id
     * @param int $level
     * @param int $distribution
     * @return Response
     * @throws \Throwable
     */
    public function receive($user_id, $id, $level, $distribution)
    {
        DB::beginTransaction();
        try {
            $coupons = DB::table('coupons')->where(['id' => $id])->lockForUpdate()->first();
            if (is_null($coupons)) {
                throw new \Exception(self::COUPONS_NOT_FOUND, 404);
            }
            if ($coupons->total_count <= 0) {
                throw new \Exception(self::COUPONS_NOT_ENOUGH, 400);
            }
            if ($coupons->limit_level > 0 && $coupons->limit_level > (int)$level) {
                throw new \Exception(self::COUPONS_LEVEL_TOO_LOW, 400);
            }
            if ($coupons->limit_distribution > 0
                && !in_array($coupons->limit_distribution, explode(',', $distribution))// 没有加入推广规则
            ) {
                throw new \Exception(self::COUPONS_DISTRIBUTION, 400);
            }
            if ($coupons->limit_count > 0) {
                $hasCount = DB::table('manages')->where(['coupon_id' => $id, 'user_id' => $user_id])->count();
                if ($hasCount >= $coupons->limit_count) {
                    throw new \Exception(self::COUPONS_TOO_MANY, 400);
                }
            }
            DB::table('manages')->insertGetId([
                'user_id'     => $user_id,
                'shop_id'     => $coupons->shop_id,
                'used'        => 'false',
                'amount'      => 0,
                'order_num'   => '',
                'coupon_id'   => $id,
                'pistolgraph' => json_encode($coupons),
                'create_time' => time(),
                'update_time' => time(),
            ]);
            DB::table('coupons')->where(['id' => $id])->decrement('total_count', 1);
            DB::commit();
            return $this->json(null);
        } catch (\Throwable $throwable) {
            DB::rollBack();
            throw $throwable;
        }
    }

    /**
     * @param Request $request
     * @param int $user_id
     * @param int $page
     * @return Response
     * @throws \Exception
     */
    public function has(Request $request, $user_id, $page)
    {
        $this->validate($request, [
            'type'      => 'in:use,not_use,not_effective,effective,expiry',
            'shop_id'   => 'integer|alpha_num',
            'page_size' => 'integer|min:1',
        ]);
        $where[] = ['user_id', '=', $user_id,];
        $currentTime = time();
        switch ($type = $request->input('type', null)) {
            case 'use':
                $where[] = ['used', '=', 'true'];
                break;
            case 'not_use':
                $where[] = ['used', '=', 'false'];
                break;
            case 'not_effective':
                $where[] = ['pistolgraph->effect_time', '>', $currentTime];
                break;
            case 'effective':
                $where[] = ['pistolgraph->effect_time', '<', $currentTime];
                $where[] = ['pistolgraph->expiry_time', '>', $currentTime];
                break;
            case 'expiry':
                $where[] = ['pistolgraph->expiry_time', '<', $currentTime];
                break;
        }
        $shopId = $request->input('shop_id', null);
        $base = DB::table('manages')->where($where)->when($shopId, function ($query) use ($shopId) {
            $query->where('shop_id', $shopId);
        });
        $page_size = $request->input('page_size', $this->page_size);
        $count = $base->count('id');
        $totalPage = ceil($count / $page_size);
        if ($totalPage == 0) {
            throw new \Exception(self::COUPONS_SHOP_NOT_FOUND, 404);
        }
        $page = ($page <= 1) ? 1 : $page;
        $coupons = $base->orderBy('id', 'desc')->offset(($page - 1) * $page_size)->limit($page_size)->get()->map(function ($value) {
            $pistolgraph = json_decode($value->pistolgraph);
            unset($value->pistolgraph, $pistolgraph->id);
            $value->coupons = $pistolgraph;
            return $value;
        });
        return $this->json([
            'page_size'    => (int)$page_size,
            'count'        => (int)$count,
            'total_page'   => (int)$totalPage,
            'current_page' => (int)$page,
            'manages'      => $coupons,
        ]);
    }

    /*----------------------------------------------------------------------*/

    /**
     * @param Request $request
     * @return Response
     * @throws \Throwable
     */
    public function useCoupon(Request $request)
    {
        $this->validate($request, [
            'orderNo' => 'required|string|min:1|max:255',
            'coupons' => 'required|array',
            'items'   => 'array',
            'amount'  => 'required|integer|min:1',
            'userId'  => 'required|alpha_num',
        ]);
        DB::beginTransaction();
        try {
            $coupons_id = $request->input('coupons', []);
            $userId = $request->input('userId');
            $amount = $request->input('amount');
            $order_num = $request->input('orderNo');
            $items = $request->input('items');
            $manages = DB::table('manages')->whereIn('id', $coupons_id)->where(['user_id' => $userId])->lockForUpdate()->get();
            if ($manages->isEmpty() || $manages->count() != count($coupons_id)) {
                throw new \Exception(self::COUPONS_SHOP_NOT_FOUND, 404);
            }
            $appoints = $commons = $appointsPrice = [];
            $foundCommodity = function ($id) use ($items) {
                foreach ($items as $item) {
                    if ($item['id'] == $id) {
                        return $item;
                    }
                }
                throw new \Exception(self::COUPONS_NOT_FOUND, 404);
            };
            $currentTime = time();
            $useCommodity = [];
            foreach ($manages as $manage) {
                if ($manage->used == 'true') {
                    throw new \Exception(self::COUPONS_HAS_USED, 400);
                }
                $coupon = json_decode($manage->pistolgraph);
                $coupon->managesID = $manage->id;
                if ($coupon->effect_time > $currentTime) {
                    throw new \Exception(self::COUPONS_NOT_EFFECT, 400);
                }
                if ($coupon->expiry_time < $currentTime) {
                    throw new \Exception(self::COUPONS_EXPIRY, 400);
                }
                if ($amount < $coupon->limit_amount) {
                    throw new \Exception(self::COUPONS_SATISFY_AMOUNT, 400);
                }
                if ($coupon->limit_commodity > 0) {
                    $commodity = $foundCommodity($coupon->limit_commodity);
                    $coupon->commodity = $commodity;
                    $appoints[] = $coupon;
                    $appointsPrice[] = $commodity['price'];
                    if (in_array($coupon->limit_commodity, $useCommodity)) {
                        throw new \Exception(self::COUPONS_JUST_USE_BY_ONE, 400);
                    } else {
                        $useCommodity[] = $coupon->limit_commodity;
                    }
                } else {
                    $commons[] = $coupon;
                }
            }
            $info = [];
            // 指定
            $appointsAmount = array_reduce($appoints, function ($stack, $coupon) use ($order_num, $currentTime, &$info) {
                switch ($coupon->type) {
                    case 'amount':
                        $now = ($coupon->type_info <= $coupon->commodity['price'])
                            ? $coupon->type_info
                            : $coupon->commodity['price'];
                        break;
                    case 'percent':
                        $now = ($coupon->type_info < 100)
                            ? round((1 - $coupon->type_info / 100) * $coupon->commodity['price'], 2)
                            : $coupon->commodity['price'];
                        break;
                    default:
                        throw new \Exception('error');
                }
                DB::table('manages')->where(['id' => $coupon->managesID])->update([
                    'used'        => 'true',
                    'amount'      => $now,
                    'order_num'   => $order_num,
                    'update_time' => $currentTime,
                ]);
                $info[] = [
                    'manages_id' => $coupon->managesID,
                    'commodity'  => $coupon->limit_commodity,
                    'amount'     => $now
                ];
                return $now + $stack;
            });
            // 通用
            $commonsAmount = array_reduce($commons, function ($stack, $coupon) use ($amount, $order_num, $currentTime, &$info) {
                switch ($coupon->type) {
                    case 'amount':
                        $now = $coupon->type_info;
                        break;
                    case 'percent':
                        $now = round((1 - $coupon->type_info / 100) * $amount, 2);
                        break;
                    default:
                        throw new \Exception('error');
                }
                DB::table('manages')->where(['id' => $coupon->managesID])->update([
                    'used'        => 'true',
                    'amount'      => $now,
                    'order_num'   => $order_num,
                    'update_time' => $currentTime,
                ]);
                $info[] = [
                    'manages_id' => $coupon->managesID,
                    'commodity'  => null,
                    'amount'     => $now
                ];
                return $now + $stack;
            });
            DB::commit();
            return $this->json([
                'tradeNo' => $order_num,
                'coupons' => $coupons_id,
                'info'    => $info,
                'amount'  => $appointsAmount + $commonsAmount,
                'userId'  => $userId,
            ]);
        } catch (\Throwable $throwable) {
            DB::rollBack();
            throw $throwable;
        }
    }

    /**
     * @param Request $request
     * @return Response
     */
    public function rollback(Request $request)
    {
        $this->validate($request, [
            'order_num' => 'required|string',
        ]);
        DB::table('manages')->where('order_num', $request->input('order_num'))->update([
            'used'        => 'false',
            'update_time' => time(),
        ]);
        return $this->json(null);
    }
}
